package com.fypg.module.blooddonation.api.client.service.bloodDonationAPI;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fypg.framework.common.exception.ErrorCode;
import com.fypg.framework.common.exception.ServiceException;
import com.fypg.framework.common.util.json.JsonUtils;
import com.fypg.module.blooddonation.api.client.config.BloodStationConfig;
import com.fypg.module.blooddonation.api.client.dal.bloodPlatform.*;
import com.fypg.module.blooddonation.api.client.dal.bloodStation.*;
import com.fypg.module.blooddonation.api.client.enums.*;
import com.fypg.module.blooddonation.api.client.service.dictionary.DictionaryServiceImpl;
import com.fypg.module.blooddonation.api.client.utils.Sm4KeyUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.fypg.framework.common.exception.util.ServiceExceptionUtil.exception;

@Service
@RequiredArgsConstructor
@Slf4j
public class BloodDonationAPIServiceImpl implements BloodDonationAPIService {
    private final WebClient webClient;
    private final DictionaryServiceImpl dictionaryServiceImpl;
    private final BloodStationConfig config;
    private final ObjectMapper objectMapper;

    /**
     * 献血核查接口
     * 主要用于核查联网范围内的献血者的献血时间间隔及淘汰信息
     *
     * @param donorChkReq 献血核查请求参数
     * @return 献血核查结果
     * @ 当接口调用失败时抛出异常
     */
    @Override
    public DonorChkData donorCheck(DonorChkReq donorChkReq)  {
        String url = config.getPlatformUrl() + ApiPathConstants.BloodPlatform.DONOR_CHECK;
        BaseRequest<DonorChkReq> request = new BaseRequest<>(
                config.getAccessKey(), donorChkReq);

        try {
            PlatformResponse<List<DonorChkData>> resp = postSm4(url,request,new  TypeReference<PlatformResponse<List<DonorChkData>>>() {});

            if (resp != null && resp.getData() != null) {
                List<DonorChkData> list = resp.getData();
                DonorChkData donorChkData = null;

                if ( !list.isEmpty()) {
                    donorChkData = list.get(0); // 拿第一个
                }
                if (BloodConstants.NORMAL_RESULT_CODE.equals(resp.getResultCode())) {
                    return donorChkData;
                } else {
                    throw exception(new ErrorCode(ErrorCodeConstants.BLOOD_DONOR_CHECK_FAILED.getCode()
                    ,resp.getErrMsg() != null ? resp.getErrMsg() : ErrorCodeConstants.BLOOD_DONOR_CHECK_FAILED.getMsg()));
                }
            } else {
                throw exception(ErrorCodeConstants.BLOOD_DONOR_CHECK_FAILED);
            }
        }catch (ServiceException e){
            throw e;
        } catch (Exception e) {
            throw exception(ErrorCodeConstants.BLOOD_DONOR_CHECK_FAILED, e.getMessage());
        }
    }

    /**
     * 献血记录查询接口
     * 主要用于查询联网范围内的献血者的历次献血信息
     *
     * @param donchkTestResultsReq 献血记录查询请求参数
     * @return 献血记录查询结果
     * @ 当接口调用失败时抛出异常
     */
    @Override
    public DonchkTestResultsResp donchkTestResults(DonRecordQueryReq donchkTestResultsReq)  {
        String url = config.getPlatformUrl() + ApiPathConstants.BloodPlatform.DONATION_RECORDS;
        BaseRequest<DonRecordQueryReq> request = new BaseRequest<>(
                config.getAccessKey(), donchkTestResultsReq);

        try {
            DonchkTestResultsResp resp = postSm4(url, request, new TypeReference<DonchkTestResultsResp>() {
            });

            if (resp != null && BloodConstants.NORMAL_RESULT_CODE.equals(resp.getResultCode())) {
                return resp;
            }
            else {
                String errorMsg = null;
                if (resp != null) {
                    errorMsg = resp.getErrMsg() != null ? resp.getErrMsg() : ErrorCodeConstants.BLOOD_RECORD_QUERY_FAILED.getMsg();
                }
                throw exception(new ErrorCode(ErrorCodeConstants.BLOOD_RECORD_QUERY_FAILED.getCode(),errorMsg));
            }
        }catch (ServiceException e) {
            throw e;
        }catch (Exception e) {
            throw exception(ErrorCodeConstants.BLOOD_RECORD_QUERY_FAILED, e.getMessage());
        }
    }

    // --------------------- 字典相关（委托给 DictionaryService）---------------------

    /**
     * 获取证件类型字典（用于前端下拉框等）
     *
     * @param orgUuid 机构UUID
     * @return 字典响应
     * @ 当接口调用失败时抛出异常
     */
    public DictDownloadResp getIdentityTypeDict(String orgUuid)  {
        return dictionaryServiceImpl.getIdentityTypeDict(orgUuid);
    }

    /**
     * 获取检测总结论字典
     *
     * @param orgUuid 机构UUID
     * @return 字典响应
     * @ 当接口调用失败时抛出异常
     */
    @Override
    public DictDownloadResp getTestResultDict(String orgUuid) {
        return dictionaryServiceImpl.getTestFinalResultDict(orgUuid);
    }

    /**
     * 通用字典下载（开放给其他模块）
     *
     * @param dictionaryKey 字典键
     * @param orgUuid 机构UUID
     * @return 字典响应
     * @ 当接口调用失败时抛出异常
     */
    public DictDownloadResp downloadDictionary(String dictionaryKey, String orgUuid)  {
        return dictionaryServiceImpl.downloadDictionary(dictionaryKey, orgUuid);
    }

    /**
     * 获取访问令牌
     * 调用其他接口之前需要先获取token
     *
     *  stationCode 血站代码
     *                    FS: 佛山血站, NH: 南海血站, SS: 三水血站, GM: 高明血站, SD: 顺德血站
     * @return 访问令牌
     * @ 当获取token失败时抛出异常
     */
    @Override
    public String getToken(String bloodStationEncode)  {
        TokenReq tokenReq = new TokenReq();
        tokenReq.setUserName(config.getUserName());
        tokenReq.setUserPassword(config.getUserPassword());

        BaseRequest<TokenReq> request = new BaseRequest<>(
                config.getAccessKey(), tokenReq);
        String url = config.getBaseUrl() + StationPath.fromOrgId(bloodStationEncode) + ApiPathConstants.BloodStation.TOKEN;

        try {
            TokenResp response = postSm4(url, request, new TypeReference<TokenResp>() {
            });
            if (response != null && response.getIsSuccess().equals(Boolean.TRUE)) {
                return response.getData();
            } else {
                String errorMsg = null;
                if (response != null) {
                    errorMsg = response.getErrorMessage() != null ? response.getErrorMessage() : ErrorCodeConstants.BLOOD_TOKEN_GET_FAILED.getMsg();
                }
                throw exception( new ErrorCode(ErrorCodeConstants.BLOOD_APPOINTMENT_FAILED.getCode(),errorMsg));
            }
        }catch (ServiceException e) {
            throw e;
        }catch (Exception e) {
            throw exception(ErrorCodeConstants.BLOOD_TOKEN_GET_FAILED, e.getMessage());
        }
    }

    private String getToken() {
        return getToken(StationPath.FS.getOrgId()); // 默认 FS 的 orgId
    }


    /**
     * 创建或更新献血预约
     * 用于提交献血预约信息，支持新增和修改操作。
     *
     * @param bloodAppReq 献血预约请求参数，
     *                    包含姓名、证件信息、预约时间、献血地点、联系方式等
     * @return 预约 ID
     * @throws ServiceException 当接口调用失败或返回数据异常时抛出业务异常
     */
    @Override
    public String bloodApp(BloodAppReq bloodAppReq) {
        bloodAppReq.setToken(getToken(bloodAppReq.getBloodStationEncode()));

        BaseRequest<BloodAppReq> request = new BaseRequest<>(
                config.getAccessKey(), bloodAppReq);

        String url = config.getBaseUrl()
                + StationPath.fromOrgId(bloodAppReq.getBloodStationEncode())
                + ApiPathConstants.BloodStation.BLOOD_APP;

        try {
            ApiResponse<String> resp = postSm4(
                    url,
                    request,
                    new TypeReference<ApiResponse<String>>() {}
            );

            // 接口返回失败
            if (Boolean.FALSE.equals(resp.getIsSuccess())) {
                throw exception( new ErrorCode(ErrorCodeConstants.BLOOD_APPOINTMENT_FAILED.getCode(),resp.getErrorMessage() != null ? resp.getErrorMessage() : ErrorCodeConstants.BLOOD_APPOINTMENT_FAILED.getMsg())
                );
            }

            // 返回数据异常
            if (resp.getData() == null ) {
                throw exception(ErrorCodeConstants.BLOOD_DICTIONARY_QUERY_FAILED,
                        "预约ID不存在");
            }

            return resp.getData();

        } catch (ServiceException e) {
            // 不要二次包装业务异常，直接抛出
            throw e;
        } catch (Exception e) {
            // 非业务异常，包装为统一业务异常，保留原栈信息
            throw exception(ErrorCodeConstants.BLOOD_APPOINTMENT_FAILED, e);
        }
    }


    /**
     * 获取献血地点排班信息
     * 查询指定血站的采血地点、工作时间、预约人数等信息
     *
     *  stationCode 血站代码
     * @return 地点排班信息列表
     * @ 当接口调用失败时抛出异常
     */
    @Override
    public List<CollectPlaceData> getCollectPlace(String bloodStationEncode)  {
        String token = getToken(bloodStationEncode);

        Map<String, String> requestData = new HashMap<>();
        requestData.put("token", token);

        BaseRequest<Map<String, String>> baseRequest = new BaseRequest<>(
                config.getAccessKey(), requestData);

        String url = config.getBaseUrl() + StationPath.fromOrgId(bloodStationEncode) + ApiPathConstants.BloodStation.GET_COLLECT_PLACE;

        try {
            ApiResponse<List<CollectPlaceData>> resp = postSm4(url,baseRequest,new  TypeReference<ApiResponse<List<CollectPlaceData>>>() {});

            if (Boolean.FALSE.equals(resp.getIsSuccess())) {
                throw exception(new ErrorCode(ErrorCodeConstants.BLOOD_PLACE_SCHEDULE_QUERY_FAILED.getCode(),
                                resp.getErrorMessage() != null ? resp.getErrorMessage() : ErrorCodeConstants.BLOOD_PLACE_SCHEDULE_QUERY_FAILED.getMsg())
                        );
            }
            if (resp.getData() == null || resp.getData().isEmpty()) {
                throw exception(ErrorCodeConstants.BLOOD_DICTIONARY_QUERY_FAILED);
            }
            return  resp.getData();

        }catch (ServiceException e) {
            throw e;
        }catch (Exception e) {
            throw exception(ErrorCodeConstants.BLOOD_PLACE_SCHEDULE_QUERY_FAILED, e.getMessage());
        }
    }

    /**
     * 获取预约结果详情
     * 根据预约标识查询预约的详细信息及当前状态
     *
     *  stationCode 血站代码
     * @param request 查询预约结果请求参数
     *                包含预约标识、姓名、证件信息等
     * @return 预约详情
     * @ 当接口调用失败时抛出异常
     */
    @Override
    public AppointmentResultData getBloodApp(GetBloodAppReq request)  {
        request.setToken(getToken(request.getBloodStationEncode()));
        BaseRequest<GetBloodAppReq> baseRequest = new BaseRequest<>(
                config.getAccessKey(), request);

        String url = config.getBaseUrl() + StationPath.fromOrgId(request.getBloodStationEncode()) +ApiPathConstants.BloodStation.GET_BLOOD_APP;

        try {
            ApiResponse<AppointmentResultData> resp = postSm4(url, baseRequest, new TypeReference<ApiResponse<AppointmentResultData>>() {});
            if(Boolean.TRUE.equals(resp.getIsSuccess())) {
                return resp.getData();
            } else {
                throw exception(new ErrorCode(ErrorCodeConstants.BLOOD_APPOINTMENT_QUERY_FAILED.getCode(),
                        resp.getErrorMessage() != null ? resp.getErrorMessage() : ErrorCodeConstants.BLOOD_APPOINTMENT_QUERY_FAILED.getMsg())
                );
            }
        }catch (ServiceException e) {
            throw e;
        }catch (Exception e) {
            throw exception(ErrorCodeConstants.BLOOD_APPOINTMENT_QUERY_FAILED, e.getMessage());
        }
    }

    /**
     * 获取时间段可预约人数
     * 查询指定日期下各时间段的可预约人数情况
     *
     *  stationCode 血站代码
     * @param request 查询可预约人数请求参数
     *                包含预约日期等
     * @return 时间段预约信息列表
     * @ 当接口调用失败时抛出异常
     */
    @Override
    public List<TimeAppCountData> getTimeAppCount( TimeAppCountReq request)  {
        request.setToken(getToken(request.getBloodStationEncode()));
        BaseRequest<TimeAppCountReq> baseRequest = new BaseRequest<>(
                config.getAccessKey(), request);

        String url = config.getBaseUrl() + StationPath.fromOrgId(request.getBloodStationEncode()) + ApiPathConstants.BloodStation.GET_TIME_APP_COUNT;

        try {
            ApiResponse<List<TimeAppCountData>> resp = postSm4(url, baseRequest, new TypeReference<ApiResponse<List<TimeAppCountData>>>() {
            });

            if (Boolean.FALSE.equals(resp.getIsSuccess())) {
                throw exception(new ErrorCode(ErrorCodeConstants.BLOOD_TIME_SLOT_QUERY_FAILED.getCode(),
                                resp.getErrorMessage() != null ? resp.getErrorMessage() : ErrorCodeConstants.BLOOD_TIME_SLOT_QUERY_FAILED.getMsg()  )
                        );
            }
            if (resp.getData() == null || resp.getData().isEmpty()) {
                throw exception(ErrorCodeConstants.BLOOD_TIME_SLOT_QUERY_FAILED);
            }
            return resp.getData();

        }catch (ServiceException e) {
            throw e;
        }catch (Exception e) {
            throw exception(ErrorCodeConstants.BLOOD_TIME_SLOT_QUERY_FAILED, e.getMessage());
        }
    }

    /**
     * 获取字典数据
     * 同步血站系统的字典信息，如民族、户籍、职业、婚姻状况、文化程度等
     *
     *  stationCode 血站代码
     * @param request 字典查询请求参数
     *                包含字典类型标识：nation-民族, origin-户籍, occupation-职业,
     *                marital-婚姻状况, education-文化程度
     * @return 字典项列表
     * @ 当接口调用失败时抛出异常
     */
//    @Cacheable(
//            value = CacheConstants.BLOOD_DICT,
//            key = "T(com.fypg.module.blooddonation.api.client.enums.CacheConstants).BLOOD_DICT_KEY_PREFIX + #request.typeCode"
//    )
    @Override
    public List<DictionaryItem> getDic(DictionaryReq request)  {

        request.setToken(getToken(request.getBloodStationEncode()));
        BaseRequest<DictionaryReq> baseRequest = new BaseRequest<>(
                config.getAccessKey(), request);

        String url = config.getBaseUrl() + StationPath.fromOrgId(request.getBloodStationEncode()) + ApiPathConstants.BloodStation.GET_DIC;

        try {

            // 转换为对象
            ApiResponse<List<GetDicResponse>> resp = postSm4(url, baseRequest, new TypeReference<ApiResponse<List<GetDicResponse>>>() {
            });


            // ---- ✅ 响应业务判断逻辑 ----

            if (Boolean.FALSE.equals(resp.getIsSuccess())) {
                throw exception(new ErrorCode(ErrorCodeConstants.BLOOD_DICTIONARY_QUERY_FAILED.getCode(),
                                resp.getErrorMessage() != null ? resp.getErrorMessage() :ErrorCodeConstants.BLOOD_DICTIONARY_QUERY_FAILED.getMsg() )
                        );
            }

            if (resp.getData() == null || resp.getData().isEmpty()) {
                throw exception(ErrorCodeConstants.BLOOD_DICTIONARY_QUERY_FAILED, "字典数据为空");
            }
            List<DictionaryItem> dictionaryItemList = new ArrayList<>();
            resp.getData().forEach(item -> {
                dictionaryItemList.add(new DictionaryItem(request.getTypeCode(),item.getId(),item.getName()));
            });
            return dictionaryItemList;
        }catch (ServiceException e) {
            throw e;
        }catch (Exception e) {
            throw exception(ErrorCodeConstants.BLOOD_DICTIONARY_QUERY_FAILED, e.getMessage());
        }
    }

    private String getDictionaryItemName(List<DictionaryItem> list, Object id) {
        if (id == null) return null;

        String strId = id.toString();
        return list.stream()
                .filter(item -> strId.equals(item.getId()))
                .map(DictionaryItem::getName)
                .findFirst()
                .orElse(null);
    }


    private <T> T postSm4(String url, Object requestObj, TypeReference<T> typeReference) {
        try {
            // request -> JSON
            String requestJson = JsonUtils.toJsonString(requestObj);
            log.info("requestJson:{}", requestJson);
            // SM4 加密
            String encrypted = Sm4KeyUtil.sm4Encrypt(requestJson);
            log.info("encrypted:" + encrypted);
            log.info("[SM4] POST {} req(len={})", url, encrypted.length());

            // WebClient调用
            String encryptedResp = webClient.post()
                    .uri(url)
                    .contentType(MediaType.APPLICATION_JSON)
                    .bodyValue(encrypted)
                    .retrieve()
                    .bodyToMono(String.class)
                    .block();

            if (encryptedResp == null) {
                throw exception(ErrorCodeConstants.BLOOD_HTTP_REQUEST_FAILED, "响应为空");
            }

//             解密
            String decrypted = Sm4KeyUtil.sm4Decrypt(encryptedResp);
            log.info("[SM4] response decrypted: {}", decrypted);

            // JSON -> 泛型对象
            return objectMapper.readValue(decrypted, typeReference);

        } catch (Exception e) {
            log.error("[SM4] Request FAILED url={}, reason={}", url, e.getMessage(), e);
            throw exception(ErrorCodeConstants.BLOOD_HTTP_REQUEST_FAILED, e.getMessage());
        }
    }


    /**
     * 移除字典缓存
     */
    @CacheEvict(
            value = CacheConstants.BLOOD_DICT,
            key = "T(com.fypg.module.blooddonation.api.client.enums.CacheConstants).BLOOD_DICT_KEY_PREFIX + #bloodStationEncode"
    )
    @Override
    public void removeDictionaryCache(String bloodStationEncode) {
        // 缓存移除由注解自动处理
    }

}
